#ifndef ENVIRONMENT_H
#define ENVIRONMENT_H

#include <vector>

#include "ngl/Transformation.h"
#include "ngl/TransformStack.h"
#include "ngl/Obj.h"
#include "ngl/Vector.h"

#include "ShaderLibrary.h"
#include "Particle.h"
#include "Capsule.h"
#include "Integration.h"

/// @file Environment.h
/// @brief manages interaction between rigid bodies and boundary and fluids
/// @author Perseedoss Rajiv
/// @version 1.0
/// @date 15.07.11
/// Revision History :
/// Initial Version 15.07.11
/// @class Environment
/// @brief stores all rigid bodies and boundary and manages interaction among rigid bodies, fluids and the boundary

class Environment
{
public:
    /// @brief ctor
    Environment();

    /// @brief dtor
    ~Environment();

    /// @brief load vbo geometry
    void loadGeometry();

    /// @brief draws the obstacles
    /// @param[in] _txStack the transform stack, used for pushing and popping opengl states
    /// @param[in,out] io_sman the shader library, used for shading/rendering of the objects
    /// @param[in] _boundaryShader the shader used for boundary drawing
    /// @param[in] _obstacleShader the shader used for obstacle drawing
    void draw
            (
                ngl::TransformStack _txStack,
                ShaderLibrary* io_sman,
                const std::string _boundaryShader,
                const std::string _obstacleShader
            );

    /// @brief detect and handles collision among particles (sphere rbd and fluid particle)
    /// @param[in,out] io_currentParticle the particle affected by the collision resolution
    /// @param[out] o_hitBoundary returns whether the particle collided against the boundary
    /// @param[out] o_hitRBD returns whether the particle collided against a rigid body
    /// @param[in] _fromRBD set whether this method was called by a sphere rigid body or by a fluid particle
    void checkAndResolveCollision
            (
                Particle& io_currentParticle,
                bool& o_hitBoundary,
                bool& o_hitRBD,
                const bool _fromRBD
            );

    /// @brief update the rigid bodies movements and rotations at every simulation iteration
    void updateObstacles(Integration* _integration);

    /// @brief return a 2d list of the faces of the boundary, used for caching
    std::vector<std::vector<ngl::Vector> > getBoundaryPositionList();

    /// @brief return a list of the boundary box name, used for caching
    std::vector<std::string> getBoundaryNameList();

    /// @brief return a 2d list of the position of spheres, used for caching
    std::vector<std::vector<ngl::Vector> > getSpherePositionList();

    /// @brief return a list of the spheres id, used for caching
    std::vector<std::string> getSphereNameList();

    /// @brief return a 2d list of the position of capsules, used for caching
    std::vector<std::vector<ngl::Vector> > getCapsulePositionList();

    /// @brief return a list of the capsules id, used for caching
    std::vector<std::string> getCapsuleNameList();

    /// @brief return the list of sphere RBD
    inline std::vector<Particle>& getSphereObstacleList() { return m_sphereObstacleList; }

    /// @brief return the list of capsule RBD
    inline std::vector<Capsule>& getCapsuleObstacleList() { return m_capsuleObstacleList; }


    /// @brief return whether RBD interaction is enabled
    inline bool getObstacleEnabled() const { return m_obstacleEnabled; }

    /// @brief return the boundary dimension
    inline ngl::Vector getBoundaryDimension() const { return m_boundaryDimension; }

    /// @brief return the boundary centre position
    inline ngl::Vector getBoundaryPosition() const { return m_boundaryPosition; }

    /// @brief return whether the top face of the boundary is closed
    inline bool getBoundaryBoundTop() const { return m_boundaryBoundTop; }

    /// @brief return the restitution coefficient for fluid interaction with boundary
    inline ngl::Real getBoundaryRestitutionCoefficientForFluid() const { return m_boundaryRestitutionCoefficientForFluid; }

    /// @brief return the restitution coefficient for RBD interaction with boundary
    inline ngl::Real getBoundaryRestitutionCoefficientForRBD() const { return m_boundaryRestitutionCoefficientForRBD; }

    /// @brief return the restitution coefficient for fluid interaction with RBD
    inline ngl::Real getObstacleRestitutionCoefficient() const { return m_obstacleRestitutionCoefficient; }

    /// @brief return the resolution of the capsule drawing
    inline int getCapsuleResolution() const { return m_capsuleResolution; }

    /// @brief return whether the boundary periodic wall is enabled
    inline bool getPeriodicWallEnabled() const { return m_periodicWallEnabled; }

    /// @brief return the maximum amplitude of the periodic wall
    inline ngl::Real getPeriodicWallMaxAmplitude() const { return m_periodicWallMaxAmplitude; }

    /// @brief return the angle speed factor of the periodic wall
    inline ngl::Real getPeriodicWallSpeed() const { return m_periodicWallSpeed; }

    /// @brief return the angle of the periodic wall sine function
    inline ngl::Real getAngleOfPeriodicWall() const { return m_angleOfPeriodicWall; }

    /// @brief return the angle increment of the periodic wall sine function
    inline ngl::Real getPeriodicWallAngleIncrement() const { return m_periodicWallAngleIncrement; }



    /// @brief set whether RBD interaction is enabled
    /// @param[in] _v the new flag
    inline void setObstacleEnabled(const bool _v) { m_obstacleEnabled = _v; }

    /// @brief set the boundary dimension
    /// @param[in] _v the new value
    inline void setBoundaryDimension(const ngl::Vector _v)
    {
        m_boundaryDimension = _v;
        delete m_boundaryBBox;
        m_boundaryBBox = new ngl::BBox(m_boundaryPosition, m_boundaryDimension.m_x, m_boundaryDimension.m_y, m_boundaryDimension.m_z);
    }

    /// @brief set the boundary centre position
    /// @param[in] _v the new value
    inline void setBoundaryPosition(ngl::Vector _v) { m_boundaryPosition = _v; m_boundaryBBox->setCenter(_v); }

    /// @brief set whether the top face of the boundary is closed
    /// @param[in] _v the new flag
    inline void setBoundaryBoundTop(const bool _v) { m_boundaryBoundTop = _v; }

    /// @brief set the restitution coefficient for fluid interaction with boundary
    /// @param[in] _v the new value
    inline void setBoundaryRestitutionCoefficientForFluid(const ngl::Real _v) { m_boundaryRestitutionCoefficientForFluid = _v; }

    /// @brief set the restitution coefficient for RBD interaction with boundary
    /// @param[in] _v the new value
    inline void setBoundaryRestitutionCoefficientForRBD(const ngl::Real _v) { m_boundaryRestitutionCoefficientForRBD = _v; }

    /// @brief set the restitution coefficient for fluid interaction with RBD
    /// @param[in] _v the new value
    inline void setObstacleRestitutionCoefficient(const ngl::Real _v) { m_obstacleRestitutionCoefficient = _v; }

    /// @brief set the resolution of the capsule drawing
    /// @param[in] _v the new value
    inline void setCapsuleResolution(const int _v) { m_capsuleResolution = _v; }

    /// @brief set whether the boundary periodic wall is enabled
    /// @param[in] _v the new flag
    inline void setPeriodicWallEnabled(const bool _v) { m_periodicWallEnabled = _v; }

    /// @brief set the maximum amplitude of the periodic wall
    /// @param[in] _v the new value
    inline void setPeriodicWallMaxAmplitude(const ngl::Real _v) { m_periodicWallMaxAmplitude = _v; }

    /// @brief set the angle speed factor of the periodic wall
    /// @param[in] _v the new value
    inline void setPeriodicWallSpeed(const ngl::Real _v) { m_periodicWallSpeed = _v; }

    /// @brief set the angle of the periodic wall sine function
    /// @param[in] _v the new value
    inline void setAngleOfPeriodicWall(const ngl::Real _v) { m_angleOfPeriodicWall = _v; }

    /// @brief set the angle increment of the periodic wall sine function
    /// @param[in] _v the new value
    inline void setPeriodicWallAngleIncrement(const ngl::Real _v) { m_periodicWallAngleIncrement = _v; }


private:
    /// @brief flag to determine if RBD interaction is enabled
    bool m_obstacleEnabled;

    /// @brief boundary dimension
    ngl::Vector m_boundaryDimension;

    /// @brief boundary centre position
    ngl::Vector m_boundaryPosition;

    /// @brief boundary bounding box object used for collision handling
    ngl::BBox* m_boundaryBBox;

    /// @brief flag to determine if the top face of the boundary is closed
    bool m_boundaryBoundTop;

    /// @brief restitution coefficient for fluid interaction with boundary
    ngl::Real m_boundaryRestitutionCoefficientForFluid;

    /// @brief restitution coefficient for RBD interaction with boundary
    ngl::Real m_boundaryRestitutionCoefficientForRBD;

    /// @brief restitution coefficient for fluid interaction with RBD
    ngl::Real m_obstacleRestitutionCoefficient;

    /// @brief list of sphere RBD
    std::vector<Particle> m_sphereObstacleList;

    /// @brief list of capsule RBD
    std::vector<Capsule> m_capsuleObstacleList;

    /// @brief resolution of the capsule drawing (the number of spheres used to draw the capsule)
    int m_capsuleResolution;

    /// @brief flag to determine whether the boundary periodic wall is enabled
    bool m_periodicWallEnabled;

    /// @brief maximum amplitude of the periodic wall
    ngl::Real m_periodicWallMaxAmplitude;

    /// @brief angle speed factor of the periodic wall
    ngl::Real m_periodicWallSpeed;

    /// @brief angle of the periodic wall sine function
    ngl::Real m_angleOfPeriodicWall;

    /// @brief angle increment of the periodic wall sine function
    ngl::Real m_periodicWallAngleIncrement;

private:
    /// @brief handle collision between sphere/fluid and sphere
    /// @param[in,out] io_currentParticle the particle affected by the collision resolution
    /// @param[in] _obstacle the rigid body against which collision is checked
    bool checkAndResolveCollisionWithObstacle
                    (
                        Particle& io_currentParticle,
                        Particle& _obstacle
                    );

    /// @brief handle collision between sphere/fluid and capsule
    /// @param[in,out] io_currentParticle the particle affected by the collision resolution
    /// @param[in] _obstacle the rigid body against which collision is checked
    bool checkAndResolveCollisionWithObstacle
                    (
                        Particle& io_currentParticle,
                        Capsule& _obstacle
                    );

    /// @brief handle collision between RBD/fluid and boundary
    /// @param[in,out] io_currentParticle the particle affected by the collision resolution
    /// @param[in] _fromRBD flag to determine whether the affected particle is an RBD
    bool checkAndResolveCollisionWithBoundary
                    (
                        Particle& io_currentParticle,
                        const bool _fromRBD
                    );

    /// @brief perform the actual collision handling (calculation of new position and velocity) once collision is detected
    /// @param[in,out] io_currentParticle the particle affected by the collision resolution
    /// @param[in] _contactPoint the contact point of collision
    /// @param[in] _normal the unit surface normal at the point of collision
    /// @param[in] _penetrationDepth the penetration depth of collision
    /// @param[in] _restitutionCoefficient the restitution coefficient used to calculate the new velocity
    void resolveCollision
                    (
                        Particle& io_currentParticle,
                        const ngl::Vector _contactPoint,
                        const ngl::Vector _normal,
                        const ngl::Real _penetrationDepth,
                        const ngl::Real _restitutionCoefficient
                    );

    /// @brief update the periodic wall position at each iteration
    void updatePeriodicBoundingBox();
};

#endif // ENVIRONMENT_H
